Passare dalla programmazione CPU seriale alla programmazione GPU richiede un cambiamento di paradigma: dal ciclo elemento per elemento all' esecuzione basata su blocchi. Non vediamo più i dati come una sequenza di scalari, ma come raccolte di "blocchi" pianificate per sfruttare al massimo la larghezza di banda dell'hardware.
1. Limitato dalla memoria vs. Limitato dal calcolo
Il collo di bottiglia di un kernel è determinato dal rapporto tra operazioni matematiche e accessi alla memoria. L'addizione vettoriale è spesso limitata dalla memoria perché esegue solo un'addizione ogni tre operazioni di memoria (2 cariche, 1 salvataggio). L'hardware trascorre più tempo ad aspettare il DRAM che a calcolare.
2. Il ruolo di BLOCK_SIZE
BLOCK_SIZE definisce la granularità del parallelismo. Se è troppo piccolo, non sfruttiamo appieno le ampie pipeline di esecuzione della GPU. Una dimensione ottimale garantisce abbastanza "lavoro in corso" per saturare la banda della memoria.
3. Nascondere la latenza tramite occupazione
Occupazione è il numero di blocchi attivi sulla GPU. Sebbene non sia l'obiettivo finale, permette al pianificatore di inserire un nuovo blocco per eseguire calcoli mentre un altro attende il recupero dei dati da VRAM con latenza elevata.
4. Utilizzo dell'hardware
Per massimizzare le prestazioni, dobbiamo allineare il nostro BLOCK_SIZE con le regole di raggruppamento della memoria dell'architettura GPU, assicurando che thread consecutivi accedano a indirizzi di memoria consecutivi.